home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World 2008 September
/
PCWorld_2008-09_cd.bin
/
v cisle
/
sadanastroju
/
lightning-0.8-tb-win.xpi
/
js
/
calCachedCalendar.js
< prev
next >
Wrap
Text File
|
2008-02-18
|
21KB
|
485 lines
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Sun Microsystems, Inc. code.
*
* The Initial Developers of the Original Code are
* Philipp Kewisch <mozilla@kewis.ch>
* Daniel Boelzle <daniel.boelzle@sun.com>
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
function calCachedCalendarObserverHelper(home, isCachedObserver) {
this.home = home;
this.isCachedObserver = isCachedObserver;
}
calCachedCalendarObserverHelper.prototype = {
isCachedObserver: false,
onStartBatch: function() {
this.home.mObservers.notify("onStartBatch");
},
onEndBatch: function() {
this.home.mObservers.notify("onEndBatch");
},
onLoad: function(calendar) {
if (this.isCachedObserver) {
this.home.mObservers.notify("onLoad", [this.home]);
} else {
// start sync action after uncached calendar has been loaded.
// xxx todo, think about:
// although onAddItem et al have been called, we need to fire
// an additional onLoad completing the refresh call (->composite)
var home = this.home;
home.synchronize(
function(status) {
home.mObservers.notify("onLoad", [home]);
});
}
},
onAddItem: function(aItem) {
if (this.isCachedObserver) {
this.home.mObservers.notify("onAddItem", arguments);
}
},
onModifyItem: function(aNewItem, aOldItem) {
if (this.isCachedObserver) {
this.home.mObservers.notify("onModifyItem", arguments);
}
},
onDeleteItem: function(aItem) {
if (this.isCachedObserver) {
this.home.mObservers.notify("onDeleteItem", arguments);
}
},
onError: function(aErrNo, aMessage) {
this.home.mObservers.notify("onError", arguments);
},
onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
if (!this.isCachedObserver) {
this.home.mObservers.notify("onPropertyChanged", [this.home, aName, aValue, aOldValue]);
}
},
onPropertyDeleting: function(aCalendar, aName) {
if (!this.isCachedObserver) {
this.home.mObservers.notify("onPropertyDeleting", [this.home, aName]);
}
}
};
function calCachedCalendar(uncachedCalendar) {
this.wrappedJSObject = this;
this.mSyncQueue = [];
this.mObservers = new calListenerBag(Components.interfaces.calIObserver);
uncachedCalendar.superCalendar = this;
uncachedCalendar.addObserver(new calCachedCalendarObserverHelper(this, false));
this.mUncachedCalendar = uncachedCalendar;
this.setupCachedCalendar();
if (this.supportsChangeLog) {
var updateTimer = this.getProperty("cache.updateTimer");
if (updateTimer === null) {
updateTimer = 4; // override for changelog based providers
}
var timerCallback = {
mCalendar: this,
notify: function(timer) {
LOG("[calCachedCalendar] replay timer");
this.mCalendar.refresh();
}
};
this.mReplayTimer = Components.classes["@mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
this.mReplayTimer.initWithCallback(timerCallback,
updateTimer * 60 * 1000,
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
}
// Take care of the inital synchronization
this.refresh();
}
calCachedCalendar.prototype = {
QueryInterface: function cCC_QueryInterface(aIID) {
return doQueryInterface(this, calCachedCalendar.prototype, aIID,
[Components.interfaces.calICachedCalendar,
Components.interfaces.calICalendar,
Components.interfaces.nsISupports]);
},
mCachedCalendar: null,
mCachedObserver: null,
mUncachedCalendar: null,
mObservers: null,
mSuperCalendar: null,
mReplayTimer: null,
onCalendarUnregistering: function() {
if (this.mReplayTimer) {
this.mReplayTimer.cancel();
this.mReplayTimer = null;
}
if (this.mCachedCalendar) {
this.mCachedCalendar.removeObserver(this.mCachedObserver);
// Although this doesn't really follow the spec, we know the
// storage calendar's deleteCalendar method is synchronous.
// TODO put changes into a different calendar and delete
// afterwards.
this.mCachedCalendar.QueryInterface(Components.interfaces.calICalendarProvider)
.deleteCalendar(this.mCachedCalendar, null);
this.mCachedCalendar = null;
}
},
setupCachedCalendar: function cCC_setupCachedCalendar() {
try {
if (this.mCachedCalendar) { // this is actually a resetupCachedCalendar:
// Although this doesn't really follow the spec, we know the
// storage calendar's deleteCalendar method is synchronous.
// TODO put changes into a different calendar and delete
// afterwards.
this.mCachedCalendar.QueryInterface(Components.interfaces.calICalendarProvider)
.deleteCalendar(this.mCachedCalendar, null);
if (this.supportsChangeLog) {
// start with full sync:
this.mUncachedCalendar.resetLog();
}
} else {
var calType = getPrefSafe("calendar.cache.type", "storage");
// While technically, the above deleteCalendar should delete the
// whole calendar, this is nothing more than deleting all events
// todos and properties. Therefore the initialization can be
// skipped.
cachedCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=" + calType]
.createInstance(Components.interfaces.calICalendar);
switch (calType) {
case "memory":
if (this.supportsChangeLog) {
// start with full sync:
this.mUncachedCalendar.resetLog();
}
break;
case "storage":
var file = getCalendarDirectory();
file.append("cache.sqlite");
var uri = getIOService().newFileURI(file);
uri.spec += ("?id=" + this.id);
cachedCalendar.uri = uri;
cachedCalendar.id = this.id;
break;
default:
throw new Error("unsupported cache calendar type: " + calType);
}
cachedCalendar.setProperty("relaxedMode", true);
cachedCalendar.superCalendar = this;
if (!this.mCachedObserver) {
this.mCachedObserver = new calCachedCalendarObserverHelper(this, true);
}
cachedCalendar.addObserver(this.mCachedObserver);
this.mCachedCalendar = cachedCalendar;
}
} catch (exc) {
Components.utils.reportError(exc);
}
},
mPendingSync: null,
mSyncQueue: null,
synchronize: function cCC_synchronize(respFunc) {
this.mSyncQueue.push(respFunc);
if (this.mSyncQueue.length > 1) { // don't use mPendingSync here
LOG("[calCachedCalendar] sync in action/pending.");
return this.mPendingSync;
}
var this_ = this;
function emptyQueue(status) {
var queue = this_.mSyncQueue;
this_.mSyncQueue = [];
function execResponseFunc(func) {
try {
func(status);
} catch (exc) {
ASSERT(false, exc);
}
}
queue.forEach(execResponseFunc);
LOG("[calCachedCalendar] sync queue empty.");
var op = this_.mPendingSync;
this_.mPendingSync = null;
return op;
}
if (this.offline) {
return emptyQueue(Components.results.NS_OK);
}
if (this.supportsChangeLog) {
LOG("[calCachedCalendar] Doing changelog based sync for calendar " + this.uri.spec);
var opListener = {
onResult: function(op, result) {
if (!op || !op.isPending) {
var status = (op ? op.status : Components.results.NS_OK);
ASSERT(Components.isSuccessCode(status), "replay action failed: " + (op ? op.id : "<unknown>"));
LOG("[calCachedCalendar] replayChangesOn finished.");
emptyQueue(status);
}
}
};
this.mPendingSync = this.mUncachedCalendar.replayChangesOn(this.mCachedCalendar, opListener);
return this.mPendingSync;
}
LOG("[calCachedCalendar] Doing full sync for calendar " + this.uri.spec);
// TODO put changes into a different calendar and delete
// afterwards.
var completeListener = {
hasRenewedCalendar: false,
onGetResult: function cCC_oOC_cL_onGetResult(aCalendar,
aStatus,
aItemType,
aDetail,
aCount,
aItems) {
if (Components.isSuccessCode(aStatus)) {
if (!this.hasRenewedCalendar) {
// TODO instead of deleting the calendar and creating a new
// one, maybe we want to do a "real" sync between the
// existing local calendar and the remote calendar.
this_.setupCachedCalendar();
this.hasRenewedCalendar = true;
}
for each (var item in aItems) {
this_.mCachedCalendar.addItem(item, null);
}
}
},
onOperationComplete: function cCC_oOC_cL_onOperationComplete(aCalendar,
aStatus,
aOpType,
aId,
aDetail) {
ASSERT(Components.isSuccessCode(aStatus), "getItems failed: " + aStatus);
emptyQueue(aStatus);
}
};
this.mPendingSync = this.mUncachedCalendar.getItems(Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS,
0, null, null, completeListener);
return this.mPendingSync;
},
onOfflineStatusChanged: function cCC_onOfflineStatusChanged(aNewState) {
if (aNewState) {
// Going offline: (XXX get items before going offline?) => we may ask the user to stay online a bit longer
} else {
// Going online (start replaying changes to the remote calendar)
this.refresh();
}
},
get superCalendar() {
return this.mSuperCalendar && this.mSuperCalendar.superCalendar || this;
},
set superCalendar(val) {
return (this.mSuperCalendar = val);
},
get offline() {
return getIOService().offline;
},
get supportsChangeLog() {
return (this.mUncachedCalendar instanceof Components.interfaces.calIChangeLog);
},
get canRefresh() { // enable triggering sync using the reload button
return true;
},
refresh: function() {
if (this.mUncachedCalendar.canRefresh && !this.offline) {
return this.mUncachedCalendar.refresh(); // will trigger synchronize once the calendar is loaded
} else {
var this_ = this;
return this.synchronize(
function(status) { // fire completing onLoad for this refresh call
this_.mCachedObserver.onLoad(this_.mCachedCalendar);
});
}
},
addObserver: function(aObserver) {
this.mObservers.add(aObserver);
},
removeObserver: function(aObserver) {
this.mObservers.remove(aObserver);
},
addItem: function(item, listener) {
return this.adoptItem(item.clone(), listener);
},
adoptItem: function(item, listener) {
if (this.offline) {
ASSERT(false, "unexpected!");
if (listener) {
listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
Components.interfaces.calIOperation.ADD, null, null);
}
return null;
}
// Forwarding add/modify/delete to the cached calendar using the calIObserver
// callbacks would be advantageous, because the uncached provider could implement
// a true push mechanism firing without being triggered from within the program.
// But this would mean the uncached provider fires on the passed
// calIOperationListener, e.g. *before* it fires on calIObservers
// (because that order is undefined). Firing onOperationComplete before onAddItem et al
// would result in this facade firing onOperationComplete even though the modification
// hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
// Result is that we currently stick to firing onOperationComplete if the cached calendar
// has performed the modification, see below:
var this_ = this;
var opListener = {
onGetResult: function(calendar, status, itemType, detail, count, items) {
ASSERT(false, "unexpected!");
},
onOperationComplete: function(calendar, status, opType, id, detail) {
if (Components.isSuccessCode(status)) {
this_.mCachedCalendar.addItem(detail, listener);
} else if (listener) {
listener.onOperationComplete(this_, status, opType, id, detail);
}
}
}
return this.mUncachedCalendar.adoptItem(item, opListener);
},
modifyItem: function(newItem, oldItem, listener) {
if (this.offline) {
ASSERT(false, "unexpected!");
if (listener) {
listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
Components.interfaces.calIOperation.MODIFY, null, null);
}
return null;
}
// Forwarding add/modify/delete to the cached calendar using the calIObserver
// callbacks would be advantageous, because the uncached provider could implement
// a true push mechanism firing without being triggered from within the program.
// But this would mean the uncached provider fires on the passed
// calIOperationListener, e.g. *before* it fires on calIObservers
// (because that order is undefined). Firing onOperationComplete before onAddItem et al
// would result in this facade firing onOperationComplete even though the modification
// hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
// Result is that we currently stick to firing onOperationComplete if the cached calendar
// has performed the modification, see below:
var this_ = this;
var opListener = {
onGetResult: function(calendar, status, itemType, detail, count, items) {
ASSERT(false, "unexpected!");
},
onOperationComplete: function(calendar, status, opType, id, detail) {
if (Components.isSuccessCode(status)) {
this_.mCachedCalendar.modifyItem(detail, oldItem, listener);
} else if (listener) {
listener.onOperationComplete(this_, status, opType, id, detail);
}
}
}
return this.mUncachedCalendar.modifyItem(newItem, oldItem, opListener);
},
deleteItem: function(item, listener) {
if (this.offline) {
ASSERT(false, "unexpected!");
if (listener) {
listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
Components.interfaces.calIOperation.DELETE, null, null);
}
return null;
}
// Forwarding add/modify/delete to the cached calendar using the calIObserver
// callbacks would be advantageous, because the uncached provider could implement
// a true push mechanism firing without being triggered from within the program.
// But this would mean the uncached provider fires on the passed
// calIOperationListener, e.g. *before* it fires on calIObservers
// (because that order is undefined). Firing onOperationComplete before onAddItem et al
// would result in this facade firing onOperationComplete even though the modification
// hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
// Result is that we currently stick to firing onOperationComplete if the cached calendar
// has performed the modification, see below:
var this_ = this;
var opListener = {
onGetResult: function(calendar, status, itemType, detail, count, items) {
ASSERT(false, "unexpected!");
},
onOperationComplete: function(calendar, status, opType, id, detail) {
if (Components.isSuccessCode(status)) {
this_.mCachedCalendar.deleteItem(item, listener);
} else if (listener) {
listener.onOperationComplete(this_, status, opType, id, detail);
}
}
}
return this.mUncachedCalendar.deleteItem(item, opListener);
}
};
(function() {
function defineForwards(proto, targetName, functions, getters, gettersAndSetters) {
function defineForwardGetter(attr) {
proto.__defineGetter__(attr, function() { return this[targetName][attr]; });
}
function defineForwardGetterAndSetter(attr) {
defineForwardGetter(attr);
proto.__defineSetter__(attr, function(value) { return (this[targetName][attr] = value); });
}
function defineForwardFunction(funcName) {
proto[funcName] = function() {
var obj = this[targetName];
return obj[funcName].apply(obj, arguments);
};
}
functions.forEach(defineForwardFunction);
getters.forEach(defineForwardGetter);
gettersAndSetters.forEach(defineForwardGetterAndSetter);
}
defineForwards(calCachedCalendar.prototype, "mUncachedCalendar",
["getProperty", "setProperty", "deleteProperty"],
["sendItipInvitations", "type"], ["id", "name", "uri", "readOnly"]);
defineForwards(calCachedCalendar.prototype, "mCachedCalendar",
["getItem", "getItems", "startBatch", "endBatch"], [], []);
})();